#include "http_download.h"

#include <fcntl.h>
#include <unistd.h>

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>

#include "util.h"

// 主线程调用
error_t HttpDownloader::work(const std::string &url, const std::string &output)
{
    logInfo("fun work running, url: %s, output file: %s", url.c_str(), output.c_str());

    std::string cmd = "rm -f " + output;
    std::system(cmd.c_str());

    error_t ret = -1;
    // 获取文件大小
    long fileSize;
    ret = getFileSize(url, fileSize);
    if (ret != STATUS_SUCCESS)
    {
        logError("getFileSize failed: %d", ret);
        return ret;
    }

    // 下载线程组
    std::vector<std::thread> downloadThreads;
    // 写入磁盘线程组（目前只支持hdd，一个线程即可)
    std::vector<std::thread> writeThreads;

    // 用npx http-server --port 8080 --cors测试
    if (checkRangeSupport(url))
    {
        const size_t totalChunks = (fileSize + CHUNK_SIZE - 1) / CHUNK_SIZE;
        logDebug("totalChunks: %zu", totalChunks);

        // chunk提交到线程池里即可

        for (size_t seq = 0; seq < totalChunks; seq++)
        {
            const size_t expected =
                (seq < totalChunks - 1) ? CHUNK_SIZE : (fileSize - (totalChunks - 1) * CHUNK_SIZE);
            downloadPool.addTask([=]() { downloadFunByRange(url, seq, expected); });
        }
    }
    // todo: 这里没完善
    // 用python3 -m http.server 8080测试
    else
    {
        downloadThreads.emplace_back([=] { downloadFun(0, url, fileSize); });
    }

    writeDiskPool.addTask([=]() { writeToDisk(output, fileSize); });

    logInfo("fun work finished");
    return STATUS_SUCCESS;
}

error_t HttpDownloader::getFileSize(const std::string &url, long &fileSize)
{
    CURLhandle curl;
    if (!curl)
    {
        logError("curl is empty");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discardDataCallback);

    // 防止url输入错误
    // 连接超时10秒
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
    // 传输超时30秒
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);

    if (curl_easy_perform(curl) != CURLE_OK)
    {
        logError("curl_easy_perform failed");
        return -1;
    }

    CURLcode res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &fileSize);
    if (res != CURLE_OK || fileSize <= 0)
    {
        logError("curl_easy_getinfo failed");
        return -1;
    }
    logDebug("fileSize: %ld", fileSize);
    return STATUS_SUCCESS;
}

bool HttpDownloader::checkRangeSupport(const std::string &url)
{
    CURLhandle curl;
    if (!curl)
    {
        logError("curl is empty");
        return -1;
    }
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
    // 请求首字节
    curl_easy_setopt(curl, CURLOPT_RANGE, "bytes=0-0");

    curl_easy_perform(curl);
    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    // todo: 需要完善
    // 206=支持Range(测试发现416也可以)
    const auto isSupport = (httpCode == 206 || httpCode == 416);
    if (isSupport)
        logDebug("server support range");
    else
        logDebug("server not support range");

    return isSupport;
}

size_t HttpDownloader::curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    const size_t totalSize = size * nmemb;

    // logDebug("enter into curlWriteCallback, size * nmemb: %zu", size * nmemb);
    auto &miscInfo =
        *static_cast<std::pair<HttpDownloader *, std::shared_ptr<TrackRecevieContext>> *>(userdata);
    HttpDownloader *self = miscInfo.first;
    auto &context = miscInfo.second;

    // 将数据拷贝到预分配的buffer中
    memcpy(context->chunk.data + context->written, ptr, totalSize);
    // 更新已写入字节数
    context->written += totalSize;

    if (context->written == context->expected)
    {
        context->completed = true;
        self->orderBuffer.addChunk(std::move(context->chunk));
        // 防御性置空
        context->chunk.data = nullptr;
    }

    return totalSize;
}

void HttpDownloader::downloadFunByRange(const std::string &url, const size_t chunkSeq,
                                        const size_t expected)
{
    logDebug("downloadFunByRange: chunkSeq: %zu, expected: %zu", chunkSeq, expected);
    // logDebug("downloadFunByRange: chunkSeq: %zu, ----------11111111111111", chunkSeq);
    CURLhandle curl;
    if (!curl)
    {
        logError("curl is null");
        return;
    }

    // 是一个chunk的起始位置
    const size_t startBytesInChunk = chunkSeq * CHUNK_SIZE;
    size_t endBytesInChunk = startBytesInChunk + expected - 1;

    std::string range = std::to_string(startBytesInChunk) + "-" + std::to_string(endBytesInChunk);
    // logDebug("thread %d http range: %s", id, range.c_str());

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);

    // 需要跟踪接受数据的状态(curl的回调函数可能多次触发)
    // 创建上下文
    std::shared_ptr<TrackRecevieContext> context =
        std::make_shared<TrackRecevieContext>(chunkSeq, expected);
    auto *userdata =
        new std::pair<HttpDownloader *, std::shared_ptr<TrackRecevieContext>>(this, context);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, userdata);

    CURLcode res = curl_easy_perform(curl);
    // 请求完成后立即释放内存
    delete userdata;
    if (res != CURLE_OK)
    {
        logError("curl_easy_perform failed, %s", curl_easy_strerror(res));
    }
}

// todo:未完善
void HttpDownloader::downloadFun(const int id, const std::string &url, const size_t expected)
{
    logDebug("thread %d : startChunk: %zu, expected: %zu", id, expected);

    logDebug("fun downloadFun finish");
}

// 写入磁盘线程的线程函数
void HttpDownloader::writeToDisk(const std::string fileName, const size_t fileSize)
{
    logDebug("fun writeToDisk runing, fileSize: %zu", fileSize);

    size_t totalWritten = 0;
    Chunk chunk;
    int fdDirect = -1;
    int fdNormal = -1;

    // 打开对齐写入的文件描述符
    fdDirect = open(fileName.c_str(), O_WRONLY | O_CREAT | O_DIRECT, 0644);

    while (totalWritten < fileSize)
    {
        orderBuffer.getChunk(chunk);

        // 对齐块用O_DIRECT写入
        if (chunk.size % CHUNK_SIZE == 0)
        {
            if (pwrite(fdDirect, chunk.data, chunk.size, chunk.seq * CHUNK_SIZE) == -1)
            {
                logError("对齐写入失败: %s", strerror(errno));
                break;
            }
        }
        // 非对齐块用普通模式写入
        else
        {
            if (fdNormal == -1)
            {
                fdNormal = open(fileName.c_str(), O_WRONLY | O_CREAT, 0644);  // 延迟打开
                if (fdNormal == -1)
                {
                    logError("无法打开普通文件描述符");
                    break;
                }
            }
            if (pwrite(fdNormal, chunk.data, chunk.size, chunk.seq * CHUNK_SIZE) == -1)
            {
                logError("非对齐写入失败: %s", strerror(errno));
                break;
            }
        }

        totalWritten += chunk.size;
    }

    if (fdDirect != -1)
        close(fdDirect);
    if (fdNormal != -1)
        close(fdNormal);

    assert(totalWritten == fileSize);

    logDebug("fun writeToDisk finish");
}